// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/dns/dns_config_service_win.h"

#include "base/logging.h"
#include "base/memory/free_deleter.h"
#include "net/base/ip_address.h"
#include "net/dns/dns_protocol.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace net {

namespace {

    TEST(DnsConfigServiceWinTest, ParseSearchList)
    {
        const struct TestCase {
            const wchar_t* input;
            const char* output[4]; // NULL-terminated, empty if expected false
        } cases[] = {
            { L"chromium.org", { "chromium.org", NULL } },
            { L"chromium.org,org", { "chromium.org", "org", NULL } },
            // Empty suffixes terminate the list
            { L"crbug.com,com,,org", { "crbug.com", "com", NULL } },
            // IDN are converted to punycode
            { L"\u017c\xf3\u0142ta.pi\u0119\u015b\u0107.pl,pl",
                { "xn--ta-4ja03asj.xn--pi-wla5e0q.pl", "pl", NULL } },
            // Empty search list is invalid
            { L"", { NULL } },
            { L",,", { NULL } },
        };

        for (const auto& t : cases) {
            std::vector<std::string> actual_output, expected_output;
            actual_output.push_back("UNSET");
            for (const char* const* output = t.output; *output; ++output) {
                expected_output.push_back(*output);
            }
            bool result = internal::ParseSearchList(t.input, &actual_output);
            if (!expected_output.empty()) {
                EXPECT_TRUE(result);
                EXPECT_EQ(expected_output, actual_output);
            } else {
                EXPECT_FALSE(result) << "Unexpected parse success on " << t.input;
            }
        }
    }

    struct AdapterInfo {
        IFTYPE if_type;
        IF_OPER_STATUS oper_status;
        const WCHAR* dns_suffix;
        std::string dns_server_addresses[4]; // Empty string indicates end.
        uint16_t ports[4];
    };

    std::unique_ptr<IP_ADAPTER_ADDRESSES, base::FreeDeleter> CreateAdapterAddresses(
        const AdapterInfo* infos)
    {
        size_t num_adapters = 0;
        size_t num_addresses = 0;
        for (size_t i = 0; infos[i].if_type; ++i) {
            ++num_adapters;
            for (size_t j = 0; !infos[i].dns_server_addresses[j].empty(); ++j) {
                ++num_addresses;
            }
        }

        size_t heap_size = num_adapters * sizeof(IP_ADAPTER_ADDRESSES) + num_addresses * (sizeof(IP_ADAPTER_DNS_SERVER_ADDRESS) + sizeof(struct sockaddr_storage));
        std::unique_ptr<IP_ADAPTER_ADDRESSES, base::FreeDeleter> heap(
            static_cast<IP_ADAPTER_ADDRESSES*>(malloc(heap_size)));
        CHECK(heap.get());
        memset(heap.get(), 0, heap_size);

        IP_ADAPTER_ADDRESSES* adapters = heap.get();
        IP_ADAPTER_DNS_SERVER_ADDRESS* addresses = reinterpret_cast<IP_ADAPTER_DNS_SERVER_ADDRESS*>(adapters + num_adapters);
        struct sockaddr_storage* storage = reinterpret_cast<struct sockaddr_storage*>(addresses + num_addresses);

        for (size_t i = 0; i < num_adapters; ++i) {
            const AdapterInfo& info = infos[i];
            IP_ADAPTER_ADDRESSES* adapter = adapters + i;
            if (i + 1 < num_adapters)
                adapter->Next = adapter + 1;
            adapter->IfType = info.if_type;
            adapter->OperStatus = info.oper_status;
            adapter->DnsSuffix = const_cast<PWCHAR>(info.dns_suffix);
            IP_ADAPTER_DNS_SERVER_ADDRESS* address = NULL;
            for (size_t j = 0; !info.dns_server_addresses[j].empty(); ++j) {
                --num_addresses;
                if (j == 0) {
                    address = adapter->FirstDnsServerAddress = addresses + num_addresses;
                } else {
                    // Note that |address| is moving backwards.
                    address = address->Next = address - 1;
                }
                IPAddress ip;
                CHECK(ip.AssignFromIPLiteral(info.dns_server_addresses[j]));
                IPEndPoint ipe = IPEndPoint(ip, info.ports[j]);
                address->Address.lpSockaddr = reinterpret_cast<LPSOCKADDR>(storage + num_addresses);
                socklen_t length = sizeof(struct sockaddr_storage);
                CHECK(ipe.ToSockAddr(address->Address.lpSockaddr, &length));
                address->Address.iSockaddrLength = static_cast<int>(length);
            }
        }

        return heap;
    }

    TEST(DnsConfigServiceWinTest, ConvertAdapterAddresses)
    {
        // Check nameservers and connection-specific suffix.
        const struct TestCase {
            AdapterInfo input_adapters[4]; // |if_type| == 0 indicates end.
            std::string expected_nameservers[4]; // Empty string indicates end.
            std::string expected_suffix;
            uint16_t expected_ports[4];
        } cases[] = {
            {
                // Ignore loopback and inactive adapters.
                {
                    { IF_TYPE_SOFTWARE_LOOPBACK, IfOperStatusUp, L"funnyloop",
                        { "2.0.0.2" } },
                    { IF_TYPE_FASTETHER, IfOperStatusDormant, L"example.com",
                        { "1.0.0.1" } },
                    { IF_TYPE_USB, IfOperStatusUp, L"chromium.org",
                        { "10.0.0.10", "2001:FFFF::1111" } },
                    { 0 },
                },
                { "10.0.0.10", "2001:FFFF::1111" },
                "chromium.org",
            },
            {
                // Respect configured ports.
                {
                    { IF_TYPE_USB, IfOperStatusUp, L"chromium.org",
                        { "10.0.0.10", "2001:FFFF::1111" }, { 1024, 24 } },
                    { 0 },
                },
                { "10.0.0.10", "2001:FFFF::1111" },
                "chromium.org",
                { 1024, 24 },
            },
            {
                // Use the preferred adapter (first in binding order) and filter
                // stateless DNS discovery addresses.
                {
                    { IF_TYPE_SOFTWARE_LOOPBACK, IfOperStatusUp, L"funnyloop",
                        { "2.0.0.2" } },
                    { IF_TYPE_FASTETHER, IfOperStatusUp, L"example.com",
                        { "1.0.0.1", "fec0:0:0:ffff::2", "8.8.8.8" } },
                    { IF_TYPE_USB, IfOperStatusUp, L"chromium.org",
                        { "10.0.0.10", "2001:FFFF::1111" } },
                    { 0 },
                },
                { "1.0.0.1", "8.8.8.8" },
                "example.com",
            },
            {
                // No usable adapters.
                {
                    { IF_TYPE_SOFTWARE_LOOPBACK, IfOperStatusUp, L"localhost",
                        { "2.0.0.2" } },
                    { IF_TYPE_FASTETHER, IfOperStatusDormant, L"example.com",
                        { "1.0.0.1" } },
                    { IF_TYPE_USB, IfOperStatusUp, L"chromium.org" },
                    { 0 },
                },
            },
        };

        for (const auto& t : cases) {
            internal::DnsSystemSettings settings;
            settings.addresses = CreateAdapterAddresses(t.input_adapters);
            // Default settings for the rest.
            std::vector<IPEndPoint> expected_nameservers;
            for (size_t j = 0; !t.expected_nameservers[j].empty(); ++j) {
                IPAddress ip;
                ASSERT_TRUE(ip.AssignFromIPLiteral(t.expected_nameservers[j]));
                uint16_t port = t.expected_ports[j];
                if (!port)
                    port = dns_protocol::kDefaultPort;
                expected_nameservers.push_back(IPEndPoint(ip, port));
            }

            DnsConfig config;
            internal::ConfigParseWinResult result = internal::ConvertSettingsToDnsConfig(settings, &config);
            internal::ConfigParseWinResult expected_result = expected_nameservers.empty() ? internal::CONFIG_PARSE_WIN_NO_NAMESERVERS
                                                                                          : internal::CONFIG_PARSE_WIN_OK;
            EXPECT_EQ(expected_result, result);
            EXPECT_EQ(expected_nameservers, config.nameservers);
            if (result == internal::CONFIG_PARSE_WIN_OK) {
                ASSERT_EQ(1u, config.search.size());
                EXPECT_EQ(t.expected_suffix, config.search[0]);
            }
        }
    }

    TEST(DnsConfigServiceWinTest, ConvertSuffixSearch)
    {
        AdapterInfo infos[2] = {
            { IF_TYPE_USB, IfOperStatusUp, L"connection.suffix", { "1.0.0.1" } },
            { 0 },
        };

        const struct TestCase {
            struct {
                internal::DnsSystemSettings::RegString policy_search_list;
                internal::DnsSystemSettings::RegString tcpip_search_list;
                internal::DnsSystemSettings::RegString tcpip_domain;
                internal::DnsSystemSettings::RegString primary_dns_suffix;
                internal::DnsSystemSettings::DevolutionSetting policy_devolution;
                internal::DnsSystemSettings::DevolutionSetting dnscache_devolution;
                internal::DnsSystemSettings::DevolutionSetting tcpip_devolution;
            } input_settings;
            std::string expected_search[5];
        } cases[] = {
            {
                // Policy SearchList override.
                {
                    { true, L"policy.searchlist.a,policy.searchlist.b" },
                    { true, L"tcpip.searchlist.a,tcpip.searchlist.b" },
                    { true, L"tcpip.domain" },
                    { true, L"primary.dns.suffix" },
                },
                { "policy.searchlist.a", "policy.searchlist.b" },
            },
            {
                // User-specified SearchList override.
                {
                    { false },
                    { true, L"tcpip.searchlist.a,tcpip.searchlist.b" },
                    { true, L"tcpip.domain" },
                    { true, L"primary.dns.suffix" },
                },
                { "tcpip.searchlist.a", "tcpip.searchlist.b" },
            },
            {
                // Void SearchList. Using tcpip.domain
                {
                    { true, L",bad.searchlist,parsed.as.empty" },
                    { true, L"tcpip.searchlist,good.but.overridden" },
                    { true, L"tcpip.domain" },
                    { false },
                },
                { "tcpip.domain", "connection.suffix" },
            },
            {
                // Void SearchList. Using primary.dns.suffix
                {
                    { true, L",bad.searchlist,parsed.as.empty" },
                    { true, L"tcpip.searchlist,good.but.overridden" },
                    { true, L"tcpip.domain" },
                    { true, L"primary.dns.suffix" },
                },
                { "primary.dns.suffix", "connection.suffix" },
            },
            {
                // Void SearchList. Using tcpip.domain when primary.dns.suffix is empty
                {
                    { true, L",bad.searchlist,parsed.as.empty" },
                    { true, L"tcpip.searchlist,good.but.overridden" },
                    { true, L"tcpip.domain" },
                    { true, L"" },
                },
                { "tcpip.domain", "connection.suffix" },
            },
            {
                // Void SearchList. Using tcpip.domain when primary.dns.suffix is NULL
                {
                    { true, L",bad.searchlist,parsed.as.empty" },
                    { true, L"tcpip.searchlist,good.but.overridden" },
                    { true, L"tcpip.domain" },
                    { true },
                },
                { "tcpip.domain", "connection.suffix" },
            },
            {
                // No primary suffix. Devolution does not matter.
                {
                    { false },
                    { false },
                    { true },
                    { true },
                    { { true, 1 }, { true, 2 } },
                },
                { "connection.suffix" },
            },
            {
                // Devolution enabled by policy, level by dnscache.
                {
                    { false },
                    { false },
                    { true, L"a.b.c.d.e" },
                    { false },
                    { { true, 1 }, { false } }, // policy_devolution: enabled, level
                    { { true, 0 }, { true, 3 } }, // dnscache_devolution
                    { { true, 0 }, { true, 1 } }, // tcpip_devolution
                },
                { "a.b.c.d.e", "connection.suffix", "b.c.d.e", "c.d.e" },
            },
            {
                // Devolution enabled by dnscache, level by policy.
                {
                    { false },
                    { false },
                    { true, L"a.b.c.d.e" },
                    { true, L"f.g.i.l.j" },
                    { { false }, { true, 4 } },
                    { { true, 1 }, { false } },
                    { { true, 0 }, { true, 3 } },
                },
                { "f.g.i.l.j", "connection.suffix", "g.i.l.j" },
            },
            {
                // Devolution enabled by default.
                {
                    { false },
                    { false },
                    { true, L"a.b.c.d.e" },
                    { false },
                    { { false }, { false } },
                    { { false }, { true, 3 } },
                    { { false }, { true, 1 } },
                },
                { "a.b.c.d.e", "connection.suffix", "b.c.d.e", "c.d.e" },
            },
            {
                // Devolution enabled at level = 2, but nothing to devolve.
                {
                    { false },
                    { false },
                    { true, L"a.b" },
                    { false },
                    { { false }, { false } },
                    { { false }, { true, 2 } },
                    { { false }, { true, 2 } },
                },
                { "a.b", "connection.suffix" },
            },
            {
                // Devolution disabled when no explicit level.
                {
                    { false },
                    { false },
                    { true, L"a.b.c.d.e" },
                    { false },
                    { { true, 1 }, { false } },
                    { { true, 1 }, { false } },
                    { { true, 1 }, { false } },
                },
                { "a.b.c.d.e", "connection.suffix" },
            },
            {
                // Devolution disabled by policy level.
                {
                    { false },
                    { false },
                    { true, L"a.b.c.d.e" },
                    { false },
                    { { false }, { true, 1 } },
                    { { true, 1 }, { true, 3 } },
                    { { true, 1 }, { true, 4 } },
                },
                { "a.b.c.d.e", "connection.suffix" },
            },
            {
                // Devolution disabled by user setting.
                {
                    { false },
                    { false },
                    { true, L"a.b.c.d.e" },
                    { false },
                    { { false }, { true, 3 } },
                    { { false }, { true, 3 } },
                    { { true, 0 }, { true, 3 } },
                },
                { "a.b.c.d.e", "connection.suffix" },
            },
        };

        for (auto& t : cases) {
            internal::DnsSystemSettings settings;
            settings.addresses = CreateAdapterAddresses(infos);
            settings.policy_search_list = t.input_settings.policy_search_list;
            settings.tcpip_search_list = t.input_settings.tcpip_search_list;
            settings.tcpip_domain = t.input_settings.tcpip_domain;
            settings.primary_dns_suffix = t.input_settings.primary_dns_suffix;
            settings.policy_devolution = t.input_settings.policy_devolution;
            settings.dnscache_devolution = t.input_settings.dnscache_devolution;
            settings.tcpip_devolution = t.input_settings.tcpip_devolution;

            DnsConfig config;
            EXPECT_EQ(internal::CONFIG_PARSE_WIN_OK,
                internal::ConvertSettingsToDnsConfig(settings, &config));
            std::vector<std::string> expected_search;
            for (size_t j = 0; !t.expected_search[j].empty(); ++j) {
                expected_search.push_back(t.expected_search[j]);
            }
            EXPECT_EQ(expected_search, config.search);
        }
    }

    TEST(DnsConfigServiceWinTest, AppendToMultiLabelName)
    {
        AdapterInfo infos[2] = {
            { IF_TYPE_USB, IfOperStatusUp, L"connection.suffix", { "1.0.0.1" } },
            { 0 },
        };

        const struct TestCase {
            internal::DnsSystemSettings::RegDword input;
            bool expected_output;
        } cases[] = {
            { { true, 0 }, false },
            { { true, 1 }, true },
            { { false, 0 }, false },
        };

        for (const auto& t : cases) {
            internal::DnsSystemSettings settings;
            settings.addresses = CreateAdapterAddresses(infos);
            settings.append_to_multi_label_name = t.input;
            DnsConfig config;
            EXPECT_EQ(internal::CONFIG_PARSE_WIN_OK,
                internal::ConvertSettingsToDnsConfig(settings, &config));
            EXPECT_EQ(t.expected_output, config.append_to_multi_label_name);
        }
    }

    // Setting have_name_resolution_policy_table should set unhandled_options.
    TEST(DnsConfigServiceWinTest, HaveNRPT)
    {
        AdapterInfo infos[2] = {
            { IF_TYPE_USB, IfOperStatusUp, L"connection.suffix", { "1.0.0.1" } },
            { 0 },
        };

        const struct TestCase {
            bool have_nrpt;
            bool unhandled_options;
            internal::ConfigParseWinResult result;
        } cases[] = {
            { false, false, internal::CONFIG_PARSE_WIN_OK },
            { true, true, internal::CONFIG_PARSE_WIN_UNHANDLED_OPTIONS },
        };

        for (const auto& t : cases) {
            internal::DnsSystemSettings settings;
            settings.addresses = CreateAdapterAddresses(infos);
            settings.have_name_resolution_policy = t.have_nrpt;
            DnsConfig config;
            EXPECT_EQ(t.result,
                internal::ConvertSettingsToDnsConfig(settings, &config));
            EXPECT_EQ(t.unhandled_options, config.unhandled_options);
            EXPECT_EQ(t.have_nrpt, config.use_local_ipv6);
        }
    }

} // namespace

} // namespace net
